#include "city_editor.h"
#include "city_map.h"
#include "city_path.h"
#include "city_node.h"

#include "input/input.h"
#include "script/scripthelper.h"
#include <luabind/luabind.hpp>
#include "script/write_lua.h"
#include "graphics/graphics.h"

#include "math/spline2d.h"
#include "console/console.h"
#include "font/font_manager.h"
#include "dd/dd_man.h"
#include "ui/ui_manager.h"

#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <cassert>
#include <algorithm>

using namespace input;
using namespace city;
using namespace script;

namespace {
///////////////////////////////////////////
// brushes
class SplineBrush : public EditorBrush
{
public:
	SplineBrush(CityEditor* pEditor, int Type) :
		EditorBrush(pEditor, "Spline"),
			m_CurrentKnot(-1),
			m_Type(Type),
			m_From(-1),
			m_To(-1)
	{
		int j=0;
	}
	SplineBrush(CityEditor* pEditor, const math::SplineCache2D& Spline) :
		EditorBrush(pEditor, "Spline"),
			m_CurrentKnot(-1),
			m_Type(0),
			m_From(-1),
			m_To(-1)
	{
		for (int i=0; i<(int)Spline.GetNodeC(); i++)
		{
			m_Spline.InsertNode(Spline.GetNode(i)->Pos);
		}
	}
	virtual ~SplineBrush()
	{
	}

	virtual void	Render()
	{
		dd::Manager::Instance().AddSpline2(&m_Spline, 0.01f, 0xFFFFFFFF, true, m_pEditor->GetScene()->GetOffset());
		math::Vec2	BoxSize(5.0f/800.0f, 5.0f/600.0f);
		math::Vec2	P = m_Pos - m_pEditor->GetScene()->GetOffset();
		dd::Manager::Instance().AddBox2D(P-BoxSize, P+BoxSize, ColorRGBA(255, 0, 0, 255));
	}

	virtual void	SetPos(const math::Vec2& Pos) 
	{ 
		EditorBrush::SetPos(Pos + m_pEditor->GetScene()->GetOffset());
		if (m_Spline.GetNodeC() <= 0)
		{
			m_Spline.InsertNode(Pos);
		}
		if (m_CurrentKnot > 0)
		{
			m_Spline.SetNodePos(m_CurrentKnot, m_Pos);
		} else
		{
			m_Spline.SetNodePos(m_Spline.GetNodeC()-1, m_Pos);
		}
	}
	virtual bool	TryDelete(const math::Vec2& Pos) 
	{ 
		if (m_CurrentKnot >= 0)
		{
			m_Spline.DeleteNode(m_CurrentKnot);
			m_CurrentKnot = -1;
			return true;
		}
		int	NodeHit = -1;
		if (m_Spline.NodeHitTest(m_Pos, &NodeHit))
		{
			m_Spline.DeleteNode(NodeHit);
			m_CurrentKnot = -1;
			return true;
		}
		return false; 
	};		// see if we have any sub-components wants deleting

	virtual void	Place()
	{
		int	NodeHit = -1;
		if (m_CurrentKnot == -1 && m_Spline.NodeHitTest(m_Pos, &NodeHit))
		{
			// make sure it's not clicking on the last node in the set
			if (NodeHit != 0 && NodeHit != m_Spline.GetNodeC() -1)
			{
				m_CurrentKnot = NodeHit;
				return;
			}
		}
		CityNode*	pNode = m_pEditor->GetScene()->GetNode(m_Pos);
		if (m_CurrentKnot == -1)
		{
			if (m_Spline.GetNodeC()<=1)
			{
				if (pNode)
					m_From = pNode->GetID();
				else
					return;
			} 
    		m_Spline.InsertNode(m_Pos);
			if (m_Spline.GetNodeC() > 2 && pNode)
			{
				m_To = pNode->GetID();
				m_pEditor->GetScene()->AddPath(m_Spline, m_From, m_To, m_Type);

				m_CurrentKnot = -1;
				m_Spline.Clear();
			}
		}
		m_CurrentKnot = -1;
	}

	math::SplineCache2D		m_Spline;
	int						m_CurrentKnot;
	int						m_From, m_To;
	int						m_Type;
	bool					m_AI;
};


class NodeBrush : public EditorBrush
{
public:
	NodeBrush(CityEditor* pEditor, const std::string& Name) :
		EditorBrush(pEditor, Name),
		m_pNode(0)
	{
		m_pNode = new CityNode(-1, pEditor->GetScene());
		m_pNode->SetType(Name);
		m_pNode->SetQuality(1);
	}
	
   	virtual ~NodeBrush()
	{
		delete m_pNode;
	}

	virtual void	Render()
	{
		m_pNode->SetPos(m_Pos+ m_pEditor->GetScene()->GetOffset());
		m_pNode->RenderDebug(m_pEditor->GetScene()->GetOffset());
	}
	virtual void	OnEnter()
	{
	}
	virtual void	Place()
	{
		m_pEditor->GetScene()->AddNode(m_Pos + m_pEditor->GetScene()->GetOffset(), m_Name, m_pNode->GetQuality());
	}

	virtual void	Rotate(int Dir)
	{
		if (Dir > 0)
			Raise(1);
		else
			Raise(-1);
	}

	virtual void	Raise(int Dir) 
	{
		m_pNode->SetQuality(m_pNode->GetQuality()+Dir);
	}

protected:
	CityNode*		m_pNode;
};
} //namespace;

CityEditor::CityEditor(CityMap* pScene)  :
	m_pScene(pScene),
	m_pBrush(NULL),
	m_SnapToGrid(false),
	m_ShowHelp(true)
{
	SetHelp("Editor Help [H]:						\n" 
			"[R]	Edit Road						\n" 
			"[S]	Edit Sea Lane					\n" 

			"[C]	Edit city						\n" 

			"[U]	Toggle UI on/off				\n"
			"[H]	Toggle Help text off			\n"
			"[F9]	Save							\n"
			"[F10]	Load							\n");


}

CityEditor::~CityEditor()
{
	UTIL_SAFE_DELETE(m_pBrush);
}

void	CityEditor::Tick(float dt)
{
}	

void	CityEditor::Render()
{
	if (m_pBrush) m_pBrush->Render();
	if (m_ShowHelp)
	{
		FontManager::Instance().Print(math::Vec2(0.01f, 0.1f), m_HelpText.c_str());
	}
}

void	CityEditor::Init()
{
	console::Console::Instance().Do("OnStartEditor()");

}

void	CityEditor::Shutdown()
{
	ClearBrush();
	console::Console::Instance().Do("OnStopEditor()");
}


void	CityEditor::MouseButtonPressed(input::Mouse::BUTTON_STATE* pKey, const math::Vec2& Pos)
{
	if (ui::UIManager::Instance().MouseInside(Pos)) return;
	math::Vec2	P = Pos+m_pScene->GetOffset();
	if (pKey->ButtonID == input::Mouse::BUTTON_RIGHT)
	{
		// give the brush a chance to delete stuff
		if (m_pBrush && m_pBrush->TryDelete(P))
		{
			return;	
		}
		// try to delete a decal?
		if (m_pScene->DeleteNode(P))
		{
				return;
		}

		// look for splines to delete
		if (m_pScene->GetPath(P))
		{
			return;
		}
	}

	if (pKey->ButtonID == input::Mouse::BUTTON_LEFT)
	{
		if (!m_pBrush)
		{
			// pickup?
			CityPath* pPath = m_pScene->GetPath(P);
			if (pPath)
			{
				UTIL_SAFE_DELETE(m_pBrush);
				m_pBrush = new SplineBrush(this, pPath->GetSpline());
			}
		} else
		{
			m_pBrush->Place();
		}
	}
}

void	CityEditor::MouseWheelMove(int WheelInfo, const math::Vec2& Pos)
{
	if (m_pBrush) m_pBrush->Rotate(WheelInfo / 30.0f);
}

void	CityEditor::MouseMove(const math::Vec2& From, const math::Vec2& To)
{
	math::Vec2	Pos = To;
	m_MousePos = Pos;
	if (m_SnapToGrid)
	{
		const float GRID_SIZE = 10.0f;
		int X = int(Pos.x * GRID_SIZE);
		int Y = int(Pos.y * GRID_SIZE);

		Pos = math::Vec2(X/GRID_SIZE, Y/GRID_SIZE);
	}
	if (m_pBrush) 
	{
		Keyboard::KEY*	pLeft = Input::Instance().GetKB()->GetKey(DIK_LSHIFT);
		Keyboard::KEY*	pRight = Input::Instance().GetKB()->GetKey(DIK_RSHIFT);
		if (pLeft->KeyDown || pRight->KeyDown)
		{
			m_pBrush->Resize(To-From);
		} else
		{
			m_pBrush->SetPos(Pos);
		}
	}
		
	m_MousePos = To;
}


void	CityEditor::KeyDown(input::Keyboard::KEY* pKey)
{
}



void	CityEditor::KeyPressed(input::Keyboard::KEY* pKey)
{
	console::Console& Con = console::Console::Instance();
	switch (pKey->ScanCode)
	{
		case DIK_LCONTROL:
		case DIK_RCONTROL:	if (m_pBrush) m_pBrush->Toggle(); break;
							
		case DIK_SPACE:	UTIL_SAFE_DELETE(m_pBrush); break;
		case DIK_G:		m_SnapToGrid = !m_SnapToGrid; break;
		case DIK_H:		m_ShowHelp = !m_ShowHelp; break;

		case DIK_LEFT:	if (m_pBrush) m_pBrush->Rotate(-90); break;
		case DIK_RIGHT:	if (m_pBrush) m_pBrush->Rotate(90); break;

		case DIK_DOWN:	if (m_pBrush) m_pBrush->Raise(-1); break;
		case DIK_UP:	if (m_pBrush) m_pBrush->Raise(1); break;
		case DIK_PRIOR:	if (m_pBrush) m_pBrush->Raise(-10); break;
		case DIK_NEXT:	if (m_pBrush) m_pBrush->Raise(10); break;
		case DIK_HOME:	if (m_pBrush) m_pBrush->Raise(100); break;
		case DIK_END:	if (m_pBrush) m_pBrush->Raise(-100); break;


		case DIK_F9:	Con.Do("SaveEditorCity()"); break;
		case DIK_F10:	Con.Do("LoadEditorCity()"); break;
		case DIK_S:	
			{
				UTIL_SAFE_DELETE(m_pBrush);
				m_pBrush = new SplineBrush(this, 1);
			}break;
		case DIK_R:	
			{
				UTIL_SAFE_DELETE(m_pBrush);
				m_pBrush = new SplineBrush(this, 0);
			}break;
		case DIK_C:	
			{
				UTIL_SAFE_DELETE(m_pBrush);
				m_pBrush = new NodeBrush(this, "CITY");
			}break;
		case DIK_RETURN:	if (m_pBrush) m_pBrush->OnEnter(); break;
		case DIK_U:		
			{
				ui::UIManager::Instance().SetVisible(!ui::UIManager::Instance().IsVisible());
			}break;

	}
}

void	CityEditor::ClearBrush()
{
	UTIL_SAFE_DELETE(m_pBrush);
}


void	CityEditor::AddNodeBrush(const std::string& Name)
{
	UTIL_SAFE_DELETE(m_pBrush);
	m_pBrush = new NodeBrush(this, Name);
}


void	CityEditor::Register(LuaPlus::LuaState* _pScript)
{
	lua_State * L = _pScript->GetCState ( );

	luabind::module(L)
    [
    luabind::class_<CityEditor>( "CityEditor" )
		.def("AddNodeBrush",		AddNodeBrush)
	];
}